package com.wudao.ncmplay.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.wudao.ncmplay.config.RepeatReadStream;
import com.wudao.ncmplay.pojo.MusicInfo;
import com.wudao.ncmplay.service.NcmService;
import com.wudao.ncmplay.util.AesUtils;
import com.wudao.ncmplay.util.ByteUtil;
import org.jaudiotagger.audio.AudioFile;
import org.jaudiotagger.audio.AudioFileIO;
import org.jaudiotagger.audio.exceptions.CannotReadException;
import org.jaudiotagger.audio.exceptions.CannotWriteException;
import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
import org.jaudiotagger.tag.FieldDataInvalidException;
import org.jaudiotagger.tag.FieldKey;
import org.jaudiotagger.tag.Tag;
import org.jaudiotagger.tag.TagException;
import org.jaudiotagger.tag.datatype.Artwork;
import org.springframework.stereotype.Service;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;

@Service
public class NcmServiceImpl implements NcmService
{
    private final byte[] coreKey = ByteUtil.hexStr2Byte("687A4852416D736F356B496E62617857");
    private final byte[] metaKey = ByteUtil.hexStr2Byte("2331346C6A6B5F215C5D2630553C2728");

    @Override
    public byte[] readKey(RepeatReadStream stream) throws IOException
    {
        byte[] arr = ByteUtil.fillByteArr();
        int keyLength = readLength(stream);
        RepeatReadStream.ValidByte keyValidByte = stream.readFile(keyLength);
        byte[] encryptedKey = keyValidByte.getValidByte();
        for (int i = 0; i < encryptedKey.length; i++) {
            encryptedKey[i] ^= 0x64;
        }
        //使用AES工具解密
        byte[] bytes = AesUtils.decrypt(coreKey, encryptedKey);
        if (bytes == null) {
            throw new RuntimeException("无法解密");
        }
        String key = removeChar(new String(bytes), 17);
        bytes = key.getBytes();
        for (int i = 0, j = 0; i < arr.length; i++) {
            j = (j + arr[i] + bytes[i % bytes.length]) & 0xFF;
            byte tmp = arr[i];
            arr[i] = arr[j];
            arr[j] = tmp;
        }
        return arr;
    }

    @Override
    public String readMetaData(RepeatReadStream stream) throws IOException
    {
        int meteLength = readLength(stream);
        byte[] meteDate = stream.readFile(meteLength).getValidByte();
        for (int i = 0; i < meteDate.length; i++) {
            meteDate[i] ^= 0x63;
        }
        meteDate = Arrays.copyOfRange(meteDate, 22, meteDate.length);
        meteDate = Base64.getDecoder().decode(meteDate);
        meteDate = AesUtils.decrypt(metaKey, meteDate);
        if (meteDate == null) {
            throw new RuntimeException("解密音频失败");
        }
        return removeChar(new String(meteDate, StandardCharsets.UTF_8), 6);
    }

    @Override
    public byte[] readAlbumCover(RepeatReadStream stream) throws IOException
    {
        stream.skip(5);
        //专辑封面图片未加密
        int coverLength = readLength(stream);
        return stream.readFile(coverLength).getValidByte();
    }

    @Override
    public byte[] readMusic(RepeatReadStream stream, byte[] keyByte)
    {
        byte[] musicBytes = stream.readFile().getValidByte();
        byte[] resultKey = new byte[keyByte.length];
        for (int i = 0; i <= 0xFF; i++) {
            resultKey[i] = keyByte[(keyByte[i] + keyByte[(i + keyByte[i]) & 0xFF]) & 0xFF];
        }
        for (int j = 0; j < musicBytes.length; j++) {
            byte cursor = resultKey[(j + 1) % resultKey.length];
            musicBytes[j] ^= cursor;
        }
        return musicBytes;
    }

    @Override
    public void setMusicTag(Tag tag, MusicInfo musicInfo) throws FieldDataInvalidException
    {
        tag.deleteArtworkField();
        tag.setField(FieldKey.ARTIST, musicInfo.getArtist());
        tag.setField(FieldKey.ALBUM, musicInfo.getAlbum());
        tag.setField(FieldKey.TITLE, musicInfo.getName());
    }

    @Override
    public String ncm2NormalFormat(File file) throws TagException, ReadOnlyFileException, CannotReadException, InvalidAudioFrameException, IOException, CannotWriteException
    {
        RepeatReadStream stream = new RepeatReadStream();
        try {
            //文件头有8位header 2位空byte
            stream.setFile(file);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            return "";
        }
        stream.skip(10);
        //音乐段byte的密码
        byte[] keyData = readKey(stream);
        //音乐文件的mete字符串
        String meteData = readMetaData(stream);
        int crc32 = readCrc32(stream);
        byte[] cover = readAlbumCover(stream);
        byte[] musicData = readMusic(stream,keyData);
        MusicInfo musicInfo = getInfo(file.getParent(), meteData);
        File saveFile = new File(file.getAbsolutePath().replace(".ncm", "." + musicInfo.getFormat()));
        stream.outPutFile(saveFile, musicData);
        AudioFile music = AudioFileIO.read(saveFile);
        Tag tag = music.getTag();
        tag.deleteArtworkField();
        setMusicTag(tag, musicInfo);
        Artwork artwork = new Artwork();
        setCover(artwork, cover);
        tag.setField(artwork);
        music.commit();
        return saveFile.getAbsolutePath();
    }

    /**
     * 读取指定字节流从0到4的长度
     * @param stream 可读字节流文件
     * @return
     * @throws IOException
     */
    private int readLength(RepeatReadStream stream) throws IOException
    {
        RepeatReadStream.ValidByte lengthBytes = stream.readFile(4);
        return ByteUtil.unpack(lengthBytes.getValidByte());
    }

    /**
     * 移除掉的是'neteasecloudmusic'字符串，但是不确定是否一定是这个，所以直接截取掉前17位
     * @param str 解码的字段
     * @param from 截取的位置
     * @return
     */
    private String removeChar(String str, int from) {
        return str.trim().replaceAll("[\r\n]", "").substring(from);
    }

    /**
     * 处理音乐文件信息
     * @param parentPath 父文件路径
     * @param metaData 音乐数据流
     * @return
     */
    private MusicInfo getInfo(String parentPath, String metaData)
    {
        JSONObject json = JSON.parseObject(metaData);
        String name = json.getString("musicName").replaceAll("/", "／");
        StringBuilder artistBuilder = new StringBuilder();
        JSONArray artists = json.getJSONArray("artist");
        for (int i = 0; i < artists.size(); i++) {
            JSONArray artist = artists.getJSONArray(i);
            artistBuilder.append(artist.getString(0)).append(",");
        }
        String artist = artistBuilder.deleteCharAt(artistBuilder.length() - 1).toString().replaceAll("/", "／");
        String album = json.getString("album");
        String format = json.getString("format");
        String filePath = parentPath + "\\" + artist + " - " + name + "." + format;
        return new MusicInfo(name, artist, album, format, filePath);
    }

    /**
     * 处理封面图片等
     * @param artwork
     * @param cover
     */
    private void setCover(Artwork artwork, byte[] cover)
    {
        artwork.setBinaryData(cover);
        artwork.setMimeType("image/jpeg");
        artwork.setPictureType(3);
    }

    /**
     * 获取字节长度？貌似没有用到
     * @return
     * @throws IOException
     */
    private int readCrc32(RepeatReadStream stream) throws IOException {
        //具体这个Crc32有什么作用还不了解
        return readLength(stream);
    }
}
